package com.liuhangs.learning.crowd.webui.exception;

import com.liuhangs.learning.crowd.webui.bean.AdminResponse;
import com.liuhangs.learning.crowd.webui.bean.exception.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;

/**
 * 全局异常处理
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({HttpMessageNotReadableException.class, MissingServletRequestParameterException.class, BindException.class,
            ServletRequestBindingException.class, MethodArgumentNotValidException.class})
    public @ResponseBody AdminResponse handleHttpMessageNotReadableException(HttpServletRequest request,
                                                                             HttpServletResponse response,
                                                                             Exception e) {
        logger.error("参数解析失败", e);
        MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));
        Set<MediaType> mediaTypeSet = new HashSet<>();
        mediaTypeSet.add(mediaType);
        request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypeSet);
        if (e instanceof BindException){
            return new AdminResponse(ErrorCode.BAD_REQUEST.getCode(), ((BindException)e).getAllErrors().get(0).getDefaultMessage());
        }
        return new AdminResponse(ErrorCode.BAD_REQUEST.getCode(), e.getMessage());
    }

    /**
     * 401  token异常处理
     * 带有@ResponseStatus注解的异常类会被ResponseStatusExceptionResolver 解析
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(TokenVerifyException.class)
    public @ResponseBody AdminResponse handleTokenVerifyException(HttpServletRequest request,
                                                                  HttpServletResponse response,
                                                                  TokenVerifyException e) {
        logger.error("token失效", e);
        //解决全局异常处理返回中文乱码问题
        MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));
        Set<MediaType> mediaTypeSet = new HashSet<>();
        mediaTypeSet.add(mediaType);
        request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypeSet);
        return new AdminResponse(ErrorCode.INVALID_TOKEN.getCode(), ErrorCode.INVALID_TOKEN.getMsg());
    }

    /**
     * 用户异常处理
     * 带有@ResponseStatus注解的异常类会被ResponseStatusExceptionResolver 解析
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(AuthenticationException.class)
    public @ResponseBody AdminResponse handleUserException(HttpServletRequest request,
                                                                  HttpServletResponse response,
                                                           AuthenticationException e) {
        logger.error("用户名或密码不正确", e);
        //解决全局异常处理返回中文乱码问题
        MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));
        Set<MediaType> mediaTypeSet = new HashSet<>();
        mediaTypeSet.add(mediaType);
        request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypeSet);
        if(e instanceof UsernameNotFoundException){
            return new AdminResponse(ErrorCode.NOT_EXIST_USER.getCode(), ErrorCode.NOT_EXIST_USER.getMsg());
        } else if(e instanceof BadCredentialsException) {
            return new AdminResponse(ErrorCode.ERROR_PWD.getCode(), ErrorCode.ERROR_PWD.getMsg());
        }
        return new AdminResponse(ErrorCode.ERROR_USER.getCode(), ErrorCode.ERROR_USER.getMsg());
    }

    /**
     * 405 - Method Not Allowed
     * 带有@ResponseStatus注解的异常类会被ResponseStatusExceptionResolver 解析
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public @ResponseBody AdminResponse handleHttpRequestMethodNotSupportedException
    (HttpServletRequest request,
     HttpServletResponse response,
     HttpRequestMethodNotSupportedException e) {
        logger.error("不支持当前请求方法", e);
        //解决全局异常处理返回中文乱码问题
        MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));
        Set<MediaType> mediaTypeSet = new HashSet<>();
        mediaTypeSet.add(mediaType);
        request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypeSet);
        return new AdminResponse(ErrorCode.METHOD_NOT_ALLOWED.getCode(), ErrorCode.METHOD_NOT_ALLOWED.getMsg());
    }

    /**
     * 其他全局异常在此捕获
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public @ResponseBody AdminResponse handleException(HttpServletRequest request,
                                                       HttpServletResponse response,
                                                       Throwable e) {
        logger.error("服务运行异常", e);
        //解决全局异常处理返回中文乱码问题
        MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));
        Set<MediaType> mediaTypeSet = new HashSet<>();
        mediaTypeSet.add(mediaType);
        request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypeSet);
        return new AdminResponse(ErrorCode.SYSTEM_ERR.getCode(), e.getMessage());
    }
}
